#include <stddef.h>
#include "LCD_Private.h"
#include "GUI_Private.h"
#include "LCD_ConfDefaults.h"
#if (!defined (LCD_LUT_COM) && !defined(LCD_LUT_SEG))
#if   (!LCD_MIRROR_X && !LCD_MIRROR_Y && !LCD_SWAP_XY) 
#define LOG2PHYS_X(x, y) x
#define LOG2PHYS_Y(x, y) y
#elif (!LCD_MIRROR_X && !LCD_MIRROR_Y &&  LCD_SWAP_XY) 
#define LOG2PHYS_X(x, y) y
#define LOG2PHYS_Y(x, y) x
#elif (!LCD_MIRROR_X &&  LCD_MIRROR_Y && !LCD_SWAP_XY) 
#define LOG2PHYS_X(x, y) x
#define LOG2PHYS_Y(x, y) LCD_YSIZE - 1 - (y)
#elif (!LCD_MIRROR_X &&  LCD_MIRROR_Y &&  LCD_SWAP_XY) 
#define LOG2PHYS_X(x, y) y
#define LOG2PHYS_Y(x, y) LCD_XSIZE - 1 - (x)
#elif ( LCD_MIRROR_X && !LCD_MIRROR_Y && !LCD_SWAP_XY) 
#define LOG2PHYS_X(x, y) LCD_XSIZE - 1 - (x)
#define LOG2PHYS_Y(x, y) y
#elif ( LCD_MIRROR_X && !LCD_MIRROR_Y &&  LCD_SWAP_XY) 
#define LOG2PHYS_X(x, y) LCD_YSIZE - 1 - (y)
#define LOG2PHYS_Y(x, y) x
#elif ( LCD_MIRROR_X &&  LCD_MIRROR_Y && !LCD_SWAP_XY) 
#define LOG2PHYS_X(x, y) LCD_XSIZE - 1 - (x)
#define LOG2PHYS_Y(x, y) LCD_YSIZE - 1 - (y)
#elif ( LCD_MIRROR_X &&  LCD_MIRROR_Y &&  LCD_SWAP_XY) 
#define LOG2PHYS_X(x, y) LCD_YSIZE - 1 - (y)
#define LOG2PHYS_Y(x, y) LCD_XSIZE - 1 - (x)
#endif
#else
#if   ( defined (LCD_LUT_COM) && !defined(LCD_LUT_SEG))
#define LOG2PHYS_X(x, y) x
#define LOG2PHYS_Y(x, y) LCD__aLine2Com0[y]
#elif (!defined (LCD_LUT_COM) &&  defined(LCD_LUT_SEG))
#define LOG2PHYS_X(x, y) LCD__aCol2Seg0[x]
#define LOG2PHYS_Y(x, y) y
#elif ( defined (LCD_LUT_COM) &&  defined(LCD_LUT_SEG))
#define LOG2PHYS_X(x, y) LCD__aCol2Seg0[x]
#define LOG2PHYS_Y(x, y) LCD__aLine2Com0[y]
#endif
#endif
typedef struct {
    U32 VRAMAddr;
    int xSize, ySize;
    int vxSize, vySize;
    int vxSizePhys;
    int BitsPerPixel;
} DRIVER_CONTEXT_TEMPLATE;
static void _SetPixelIndex(GUI_DEVICE * pDevice, int x, int y, int PixelIndex) {
    //
    // Convert logical into physical coordinates (Dep. on LCDConf.h)
    //
#if (LCD_MIRROR_X == 1) || (LCD_MIRROR_Y == 1) || (LCD_SWAP_XY == 1)
    int xPhys, yPhys;
    xPhys = LOG2PHYS_X(x, y);
    yPhys = LOG2PHYS_Y(x, y);
#else
#define xPhys x
#define yPhys y
#endif
    GUI_USE_PARA(pDevice);
    GUI_USE_PARA(x);
    GUI_USE_PARA(y);
    GUI_USE_PARA(PixelIndex);
    {
        //
        // Write into hardware ... Adapt to your system
        //
        // TBD by customer...
        //
    }
#if (LCD_MIRROR_X == 0) && (LCD_MIRROR_Y == 0) && (LCD_SWAP_XY == 0)
#undef xPhys
#undef yPhys
#endif
}
static unsigned int _GetPixelIndex(GUI_DEVICE * pDevice, int x, int y) {
    unsigned int PixelIndex;
    //
    // Convert logical into physical coordinates (Dep. on LCDConf.h)
    //
#if (LCD_MIRROR_X == 1) || (LCD_MIRROR_Y == 1) || (LCD_SWAP_XY == 1)
    int xPhys, yPhys;
    xPhys = LOG2PHYS_X(x, y);
    yPhys = LOG2PHYS_Y(x, y);
#else
#define xPhys x
#define yPhys y
#endif
    GUI_USE_PARA(pDevice);
    GUI_USE_PARA(x);
    GUI_USE_PARA(y);
    {
        //
        // Write into hardware ... Adapt to your system
        //
        // TBD by customer...
        //
        PixelIndex = 0;
    }
#if (LCD_MIRROR_X == 0) && (LCD_MIRROR_Y == 0) && (LCD_SWAP_XY == 0)
#undef xPhys
#undef yPhys
#endif
    return PixelIndex;
}
static void _XorPixel(GUI_DEVICE * pDevice, int x, int y) {
    LCD_PIXELINDEX PixelIndex;
    LCD_PIXELINDEX IndexMask;
    PixelIndex = _GetPixelIndex(pDevice, x, y);
    IndexMask  = pDevice->pColorConvAPI->pfGetIndexMask();
    _SetPixelIndex(pDevice, x, y, PixelIndex ^ IndexMask);
}
static void _FillRect(GUI_DEVICE * pDevice, int x0, int y0, int x1, int y1) {
    LCD_PIXELINDEX PixelIndex;
    int x;
    PixelIndex = LCD__GetColorIndex();
    if (GUI_pContext->DrawMode & LCD_DRAWMODE_XOR) {
        for (; y0 <= y1; y0++) {
            for (x = x0; x <= x1; x++) {
                _XorPixel(pDevice, x, y0);
            }
        }
    } else {
        for (; y0 <= y1; y0++) {
            for (x = x0; x <= x1; x++) {
                _SetPixelIndex(pDevice, x, y0, PixelIndex);
            }
        }
    }
}
static void _DrawHLine(GUI_DEVICE * pDevice, int x0, int y, int x1) {
    _FillRect(pDevice, x0, y, x1, y);
}
static void _DrawVLine(GUI_DEVICE * pDevice, int x, int y0, int y1) {
    _FillRect(pDevice, x, y0, x, y1);
}
static void _DrawBitLine1BPP(GUI_DEVICE * pDevice, int x, int y, U8 const GUI_UNI_PTR * p, int Diff, int xsize, const LCD_PIXELINDEX * pTrans) {
    LCD_PIXELINDEX IndexMask, Index0, Index1, Pixel;
    Index0 = *(pTrans + 0);
    Index1 = *(pTrans + 1);
    x += Diff;
    switch (GUI_pContext->DrawMode & (LCD_DRAWMODE_TRANS | LCD_DRAWMODE_XOR)) {
        case 0:
            do {
                _SetPixelIndex(pDevice, x++, y, (*p & (0x80 >> Diff)) ? Index1 : Index0);
                if (++Diff == 8) {
                    Diff = 0;
                    p++;
                }
            } while (--xsize);
            break;
        case LCD_DRAWMODE_TRANS:
            do {
                if (*p & (0x80 >> Diff))
                    _SetPixelIndex(pDevice, x, y, Index1);
                x++;
                if (++Diff == 8) {
                    Diff = 0;
                    p++;
                }
            } while (--xsize);
            break;
        case LCD_DRAWMODE_XOR | LCD_DRAWMODE_TRANS:
        case LCD_DRAWMODE_XOR:
            IndexMask = pDevice->pColorConvAPI->pfGetIndexMask();
            do {
                if (*p & (0x80 >> Diff)) {
                    Pixel = _GetPixelIndex(pDevice, x, y);
                    _SetPixelIndex(pDevice, x, y, Pixel ^ IndexMask);
                }
                x++;
                if (++Diff == 8) {
                    Diff = 0;
                    p++;
                }
            } while (--xsize);
            break;
    }
}
static void  _DrawBitLine2BPP(GUI_DEVICE * pDevice, int x, int y, U8 const GUI_UNI_PTR * p, int Diff, int xsize, const LCD_PIXELINDEX * pTrans) {
    LCD_PIXELINDEX Pixels, PixelIndex;
    int CurrentPixel, Shift, Index;
    Pixels = *p;
    CurrentPixel = Diff;
    x += Diff;
    switch (GUI_pContext->DrawMode & (LCD_DRAWMODE_TRANS | LCD_DRAWMODE_XOR)) {
        case 0:
            if (pTrans) {
                do {
                    Shift = (3 - CurrentPixel) << 1;
                    Index = (Pixels & (0xC0 >> (6 - Shift))) >> Shift;
                    PixelIndex = *(pTrans + Index);
                    _SetPixelIndex(pDevice, x++, y, PixelIndex);
                    if (++CurrentPixel == 4) {
                        CurrentPixel = 0;
                        Pixels = *(++p);
                    }
                } while (--xsize);
            } else {
                do {
                    Shift = (3 - CurrentPixel) << 1;
                    Index = (Pixels & (0xC0 >> (6 - Shift))) >> Shift;
                    _SetPixelIndex(pDevice, x++, y, Index);
                    if (++CurrentPixel == 4) {
                        CurrentPixel = 0;
                        Pixels = *(++p);
                    }
                } while (--xsize);
            }
            break;
        case LCD_DRAWMODE_TRANS:
            if (pTrans) {
                do {
                    Shift = (3 - CurrentPixel) << 1;
                    Index = (Pixels & (0xC0 >> (6 - Shift))) >> Shift;
                    if (Index) {
                        PixelIndex = *(pTrans + Index);
                        _SetPixelIndex(pDevice, x, y, PixelIndex);
                    }
                    x++;
                    if (++CurrentPixel == 4) {
                        CurrentPixel = 0;
                        Pixels = *(++p);
                    }
                } while (--xsize);
            } else {
                do {
                    Shift = (3 - CurrentPixel) << 1;
                    Index = (Pixels & (0xC0 >> (6 - Shift))) >> Shift;
                    if (Index) {
                        _SetPixelIndex(pDevice, x, y, Index);
                    }
                    x++;
                    if (++CurrentPixel == 4) {
                        CurrentPixel = 0;
                        Pixels = *(++p);
                    }
                } while (--xsize);
            }
            break;
    }
}
static void  _DrawBitLine4BPP(GUI_DEVICE * pDevice, int x, int y, U8 const GUI_UNI_PTR * p, int Diff, int xsize, const LCD_PIXELINDEX * pTrans) {
    LCD_PIXELINDEX Pixels, PixelIndex;
    int CurrentPixel, Shift, Index;
    Pixels = *p;
    CurrentPixel = Diff;
    x += Diff;
    switch (GUI_pContext->DrawMode & (LCD_DRAWMODE_TRANS | LCD_DRAWMODE_XOR)) {
        case 0:
            if (pTrans) {
                do {
                    Shift = (1 - CurrentPixel) << 2;
                    Index = (Pixels & (0xF0 >> (4 - Shift))) >> Shift;
                    PixelIndex = *(pTrans + Index);
                    _SetPixelIndex(pDevice, x++, y, PixelIndex);
                    if (++CurrentPixel == 2) {
                        CurrentPixel = 0;
                        Pixels = *(++p);
                    }
                } while (--xsize);
            } else {
                do {
                    Shift = (1 - CurrentPixel) << 2;
                    Index = (Pixels & (0xF0 >> (4 - Shift))) >> Shift;
                    _SetPixelIndex(pDevice, x++, y, Index);
                    if (++CurrentPixel == 2) {
                        CurrentPixel = 0;
                        Pixels = *(++p);
                    }
                } while (--xsize);
            }
            break;
        case LCD_DRAWMODE_TRANS:
            if (pTrans) {
                do {
                    Shift = (1 - CurrentPixel) << 2;
                    Index = (Pixels & (0xF0 >> (4 - Shift))) >> Shift;
                    if (Index) {
                        PixelIndex = *(pTrans + Index);
                        _SetPixelIndex(pDevice, x, y, PixelIndex);
                    }
                    x++;
                    if (++CurrentPixel == 2) {
                        CurrentPixel = 0;
                        Pixels = *(++p);
                    }
                } while (--xsize);
            } else {
                do {
                    Shift = (1 - CurrentPixel) << 2;
                    Index = (Pixels & (0xF0 >> (4 - Shift))) >> Shift;
                    if (Index) {
                        _SetPixelIndex(pDevice, x, y, Index);
                    }
                    x++;
                    if (++CurrentPixel == 2) {
                        CurrentPixel = 0;
                        Pixels = *(++p);
                    }
                } while (--xsize);
            }
            break;
    }
}
static void  _DrawBitLine8BPP(GUI_DEVICE * pDevice, int x, int y, U8 const GUI_UNI_PTR * p, int xsize, const LCD_PIXELINDEX * pTrans) {
    LCD_PIXELINDEX Pixel;
    switch (GUI_pContext->DrawMode & (LCD_DRAWMODE_TRANS | LCD_DRAWMODE_XOR)) {
        case 0:
            if (pTrans) {
                for (; xsize > 0; xsize--, x++, p++) {
                    Pixel = *p;
                    _SetPixelIndex(pDevice, x, y, *(pTrans + Pixel));
                }
            } else {
                for (; xsize > 0; xsize--, x++, p++) {
                    _SetPixelIndex(pDevice, x, y, *p);
                }
            }
            break;
        case LCD_DRAWMODE_TRANS:
            if (pTrans) {
                for (; xsize > 0; xsize--, x++, p++) {
                    Pixel = *p;
                    if (Pixel) {
                        _SetPixelIndex(pDevice, x, y, *(pTrans + Pixel));
                    }
                }
            } else {
                for (; xsize > 0; xsize--, x++, p++) {
                    Pixel = *p;
                    if (Pixel) {
                        _SetPixelIndex(pDevice, x, y, Pixel);
                    }
                }
            }
            break;
    }
}
static void _DrawBitLine16BPP(GUI_DEVICE * pDevice, int x, int y, U16 const GUI_UNI_PTR * p, int xsize) {
    for (;xsize > 0; xsize--, x++, p++) {
        _SetPixelIndex(pDevice, x, y, *p);
    }
}
static void _DrawBitLine32BPP(GUI_DEVICE * pDevice, int x, int y, U32 const GUI_UNI_PTR * p, int xsize) {
    for (;xsize > 0; xsize--, x++, p++) {
        _SetPixelIndex(pDevice, x, y, *p);
    }
}
static void _DrawBitmap(GUI_DEVICE * pDevice, int x0, int y0,
        int xSize, int ySize,
        int BitsPerPixel, 
        int BytesPerLine,
        const U8 GUI_UNI_PTR * pData, int Diff,
        const LCD_PIXELINDEX * pTrans) {
    int i;
    switch (BitsPerPixel) {
        case 1:
            for (i = 0; i < ySize; i++) {
                _DrawBitLine1BPP(pDevice, x0, i + y0, pData, Diff, xSize, pTrans);
                pData += BytesPerLine;
            }
            break;
        case 2:
            for (i = 0; i < ySize; i++) {
                _DrawBitLine2BPP(pDevice, x0, i + y0, pData, Diff, xSize, pTrans);
                pData += BytesPerLine;
            }
            break;
        case 4:
            for (i = 0; i < ySize; i++) {
                _DrawBitLine4BPP(pDevice, x0, i + y0, pData, Diff, xSize, pTrans);
                pData += BytesPerLine;
            }
            break;
        case 8:
            for (i = 0; i < ySize; i++) {
                _DrawBitLine8BPP(pDevice, x0, i + y0, pData, xSize, pTrans);
                pData += BytesPerLine;
            }
            break;
            //
            // Only required for 16bpp color depth of target. Should be removed otherwise.
            //
        case 16:
            for (i = 0; i < ySize; i++) {
                _DrawBitLine16BPP(pDevice, x0, i + y0, (const U16 *)pData, xSize);
                pData += BytesPerLine;
            }
            break;
            //
            // Only required for 32bpp color depth of target. Should be removed otherwise.
            //
        case 32:
            for (i = 0; i < ySize; i++) {
                _DrawBitLine32BPP(pDevice, x0, i + y0, (const U32 *)pData, xSize);
                pData += BytesPerLine;
            }
            break;
    }
}
static int _InitOnce(GUI_DEVICE * pDevice) {
    DRIVER_CONTEXT_TEMPLATE * pContext;
    if (pDevice->u.pContext == NULL) {
        pDevice->u.pContext = GUI_ALLOC_GetFixedBlock(sizeof(DRIVER_CONTEXT_TEMPLATE));
        pContext = (DRIVER_CONTEXT_TEMPLATE *)pDevice->u.pContext;
        pContext->BitsPerPixel = LCD__GetBPP(pDevice->pColorConvAPI->pfGetIndexMask());
    }
    return pDevice->u.pContext ? 0 : 1;
}
static I32 _GetDevProp(GUI_DEVICE * pDevice, int Index) {
    DRIVER_CONTEXT_TEMPLATE * pContext;
    pContext = (DRIVER_CONTEXT_TEMPLATE *)pDevice->u.pContext;
    switch (Index) {
        case LCD_DEVCAP_XSIZE:
            return pContext->xSize;
        case LCD_DEVCAP_YSIZE:
            return pContext->ySize;
        case LCD_DEVCAP_VXSIZE:
            return pContext->vxSize;
        case LCD_DEVCAP_VYSIZE:
            return pContext->vySize;
        case LCD_DEVCAP_BITSPERPIXEL:
            return pContext->BitsPerPixel;
        case LCD_DEVCAP_NUMCOLORS:
            return 0;
        case LCD_DEVCAP_XMAG:
            return 1;
        case LCD_DEVCAP_YMAG:
            return 1;
        case LCD_DEVCAP_MIRROR_X:
            return 0;
        case LCD_DEVCAP_MIRROR_Y:
            return 0;
        case LCD_DEVCAP_SWAP_XY:
            return 0;
    }
    return -1;
}
static void * _GetDevData(GUI_DEVICE * pDevice, int Index) {
    GUI_USE_PARA(pDevice);
#if GUI_SUPPORT_MEMDEV
    switch (Index) {
        case LCD_DEVDATA_MEMDEV:
            return (void *)&GUI_MEMDEV_DEVICE_16; // TBD: Has to be adapted to the right memory device depending on the used color depth!
    }
#else
    GUI_USE_PARA(Index);
#endif
    return NULL;
}
static void _GetRect(GUI_DEVICE * pDevice, LCD_RECT * pRect) {
    DRIVER_CONTEXT_TEMPLATE * pContext;
    pContext = (DRIVER_CONTEXT_TEMPLATE *)pDevice->u.pContext;
    pRect->x0 = 0;
    pRect->y0 = 0;
    pRect->x1 = pContext->vxSize - 1;
    pRect->y1 = pContext->vySize - 1;
}
static void _SetOrg(GUI_DEVICE * pDevice, int x, int y) {
    LCD_X_SETORG_INFO Data = {0};
    Data.xPos = x;
    Data.yPos = y;
    LCD_X_DisplayDriver(pDevice->LayerIndex, LCD_X_SETORG, (void *)&Data);
}
static void _SetVRAMAddr(GUI_DEVICE * pDevice, void * pVRAM) {
    DRIVER_CONTEXT_TEMPLATE * pContext;
    LCD_X_SETVRAMADDR_INFO Data = {0};
    _InitOnce(pDevice);
    if (pDevice->u.pContext) {
        pContext = (DRIVER_CONTEXT_TEMPLATE *)pDevice->u.pContext;
        pContext->VRAMAddr = (U32)pVRAM;
        Data.pVRAM = pVRAM;
        LCD_X_DisplayDriver(pDevice->LayerIndex, LCD_X_SETVRAMADDR, (void *)&Data);
    }
}
static void _SetVSize(GUI_DEVICE * pDevice, int xSize, int ySize) {
    DRIVER_CONTEXT_TEMPLATE * pContext;
    _InitOnce(pDevice);
    if (pDevice->u.pContext) {
        pContext = (DRIVER_CONTEXT_TEMPLATE *)pDevice->u.pContext;
        pContext->vxSize = xSize;
        pContext->vySize = ySize;
        pContext->vxSizePhys = xSize;
    }
}
static void _SetSize(GUI_DEVICE * pDevice, int xSize, int ySize) {
    DRIVER_CONTEXT_TEMPLATE * pContext;
    LCD_X_SETSIZE_INFO Data = {0};
    _InitOnce(pDevice);
    if (pDevice->u.pContext) {
        pContext = (DRIVER_CONTEXT_TEMPLATE *)pDevice->u.pContext;
        pContext->vxSizePhys = (pContext->vxSizePhys == 0) ? xSize : pContext->vxSizePhys;
        pContext->xSize = xSize;
        pContext->ySize = ySize;
        Data.xSize = xSize;
        Data.ySize = ySize;
        LCD_X_DisplayDriver(pDevice->LayerIndex, LCD_X_SETSIZE, (void *)&Data);
    }
}
static int  _Init(GUI_DEVICE * pDevice) {
    int r;
    r = _InitOnce(pDevice);
    r |= LCD_X_DisplayDriver(pDevice->LayerIndex, LCD_X_INITCONTROLLER, NULL);
    return r;
}
static void _On (GUI_DEVICE * pDevice) {
    LCD_X_DisplayDriver(pDevice->LayerIndex, LCD_X_ON, NULL);
}
static void _Off (GUI_DEVICE * pDevice) {
    LCD_X_DisplayDriver(pDevice->LayerIndex, LCD_X_OFF, NULL);
}
static void _SetLUTEntry(GUI_DEVICE * pDevice, U8 Pos, LCD_COLOR Color) {
    LCD_X_SETLUTENTRY_INFO Data = {0};
    Data.Pos   = Pos;
    Data.Color = Color;
    LCD_X_DisplayDriver(pDevice->LayerIndex, LCD_X_SETLUTENTRY, (void *)&Data);
}
static void (* _GetDevFunc(GUI_DEVICE ** ppDevice, int Index))(void) {
    GUI_USE_PARA(ppDevice);
    switch (Index) {
        case LCD_DEVFUNC_SET_VRAM_ADDR:
            return (void (*)(void))_SetVRAMAddr;
        case LCD_DEVFUNC_SET_VSIZE:
            return (void (*)(void))_SetVSize;
        case LCD_DEVFUNC_SET_SIZE:
            return (void (*)(void))_SetSize;
        case LCD_DEVFUNC_INIT:
            return (void (*)(void))_Init;
        case LCD_DEVFUNC_ON:
            return (void (*)(void))_On;
        case LCD_DEVFUNC_OFF:
            return (void (*)(void))_Off;
        case LCD_DEVFUNC_SETLUTENTRY:
            return (void (*)(void))_SetLUTEntry;
    }
    return NULL;
}
const GUI_DEVICE_API GUIDRV_Template_API = {
    //
    // Data
    //
    DEVICE_CLASS_DRIVER,
    //
    // Drawing functions
    //
    _DrawBitmap,
    _DrawHLine,
    _DrawVLine,
    _FillRect,
    _GetPixelIndex,
    _SetPixelIndex,
    _XorPixel,
    //
    // Set origin
    //
    _SetOrg,
    //
    // Request information
    //
    _GetDevFunc,
    _GetDevProp,
    _GetDevData,
    _GetRect,
};

